﻿using Microsoft.Extensions.Caching.Distributed;
using PmSoft.Core;
using System.Text;

namespace PmSoft.Cache.Abstractions;

/// <summary>
/// 提供对 <see cref="IDistributedCache"/> 的扩展方法，支持泛型对象操作、缓存键值存在性检查和自定义过期时间。
/// 使用自定义 Json.Parse<T> 和 Json.Stringify 进行序列化和反序列化。
/// </summary>
public static class DistributedCacheExtensions
{
	/// <summary>
	/// 将泛型对象异步设置到分布式缓存中，使用默认缓存选项。
	/// </summary>
	/// <typeparam name="T">要缓存的对象类型。</typeparam>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <param name="value">要缓存的对象。</param>
	/// <param name="cancellationToken">取消令牌，可选。</param>
	/// <returns>表示异步操作的任务。</returns>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static async Task SetAsync<T>(
		this IDistributedCache cache,
		string key,
		T value,
		CancellationToken cancellationToken = default)
	{
		await SetAsync(cache, key, value, null, cancellationToken);
	}

	/// <summary>
	/// 将泛型对象异步设置到分布式缓存中，指定过期时间。
	/// </summary>
	/// <typeparam name="T">要缓存的对象类型。</typeparam>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <param name="value">要缓存的对象。</param>
	/// <param name="expiration">过期时间，若为 null 则使用默认选项。</param>
	/// <param name="cancellationToken">取消令牌，可选。</param>
	/// <returns>表示异步操作的任务。</returns>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static async Task SetAsync<T>(
		this IDistributedCache cache,
		string key,
		T value,
		TimeSpan? expiration,
		CancellationToken cancellationToken = default)
	{
		if (cache == null) throw new ArgumentNullException(nameof(cache));
		if (key == null) throw new ArgumentNullException(nameof(key));

		var json = Json.Stringify(value);
		var bytes = Encoding.UTF8.GetBytes(json);
		var options = expiration.HasValue
			? new DistributedCacheEntryOptions { SlidingExpiration = expiration.Value }
			: new DistributedCacheEntryOptions();
		await cache.SetAsync(key, bytes, options, cancellationToken);
	}

	/// <summary>
	/// 从分布式缓存中异步获取泛型对象。
	/// </summary>
	/// <typeparam name="T">要获取的对象类型。</typeparam>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <param name="cancellationToken">取消令牌，可选。</param>
	/// <returns>缓存中的对象，若不存在或解析失败则返回 default(T)。</returns>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static async Task<T?> GetAsync<T>(
		this IDistributedCache cache,
		string key,
		CancellationToken cancellationToken = default)
	{
		if (cache == null) throw new ArgumentNullException(nameof(cache));
		if (key == null) throw new ArgumentNullException(nameof(key));

		var bytes = await cache.GetAsync(key, cancellationToken);
		if (bytes == null || bytes.Length == 0)
			return default;

		var json = Encoding.UTF8.GetString(bytes);
		return Json.Parse<T>(json);
	}

	/// <summary>
	/// 异步检查指定缓存键是否存在。
	/// </summary>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <param name="cancellationToken">取消令牌，可选。</param>
	/// <returns>如果键存在则返回 true，否则返回 false。</returns>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static async Task<bool> ExistsAsync(
		this IDistributedCache cache,
		string key,
		CancellationToken cancellationToken = default)
	{
		if (cache == null) throw new ArgumentNullException(nameof(cache));
		if (key == null) throw new ArgumentNullException(nameof(key));

		var bytes = await cache.GetAsync(key, cancellationToken);
		return bytes != null && bytes.Length > 0;
	}

	/// <summary>
	/// 将泛型对象同步设置到分布式缓存中，使用默认缓存选项。
	/// </summary>
	/// <typeparam name="T">要缓存的对象类型。</typeparam>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <param name="value">要缓存的对象。</param>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static void Set<T>(
		this IDistributedCache cache,
		string key,
		T value)
	{
		Set(cache, key, value, null);
	}

	/// <summary>
	/// 将泛型对象同步设置到分布式缓存中，指定过期时间。
	/// </summary>
	/// <typeparam name="T">要缓存的对象类型。</typeparam>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <param name="value">要缓存的对象。</param>
	/// <param name="expiration">过期时间，若为 null 则使用默认选项。</param>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static void Set<T>(
		this IDistributedCache cache,
		string key,
		T value,
		TimeSpan? expiration)
	{
		if (cache == null) throw new ArgumentNullException(nameof(cache));
		if (key == null) throw new ArgumentNullException(nameof(key));

		var json = Json.Stringify(value);
		var bytes = Encoding.UTF8.GetBytes(json);
		var options = expiration.HasValue
			? new DistributedCacheEntryOptions { SlidingExpiration = expiration.Value }
			: new DistributedCacheEntryOptions();
		cache.Set(key, bytes, options);
	}

	/// <summary>
	/// 从分布式缓存中同步获取泛型对象。
	/// </summary>
	/// <typeparam name="T">要获取的对象类型。</typeparam>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <returns>缓存中的对象，若不存在或解析失败则返回 default(T)。</returns>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static T? Get<T>(this IDistributedCache cache, string key)
	{
		if (cache == null) throw new ArgumentNullException(nameof(cache));
		if (key == null) throw new ArgumentNullException(nameof(key));

		var bytes = cache.Get(key);
		if (bytes == null || bytes.Length == 0)
			return default;

		var json = Encoding.UTF8.GetString(bytes);
		return Json.Parse<T>(json);
	}

	/// <summary>
	/// 同步检查指定缓存键是否存在。
	/// </summary>
	/// <param name="cache">分布式缓存实例。</param>
	/// <param name="key">缓存键。</param>
	/// <returns>如果键存在则返回 true，否则返回 false。</returns>
	/// <exception cref="ArgumentNullException">当 <paramref name="cache"/> 或 <paramref name="key"/> 为 null 时抛出。</exception>
	public static bool Exists(this IDistributedCache cache, string key)
	{
		if (cache == null) throw new ArgumentNullException(nameof(cache));
		if (key == null) throw new ArgumentNullException(nameof(key));

		var bytes = cache.Get(key);
		return bytes != null && bytes.Length > 0;
	}
}